#include "Shader.h"
#include "Render.h"
#include <fstream>
#include <string>
#include <sstream>
#include <iostream>
#include <GL/glew.h>

Shader::Shader(const std::string& filePath) :m_ShaderPath(filePath), m_RendererID(0)
{
	ShaderProgramSource shaderSource = ParseSharder(filePath);
	std::cout << "VertexSource: \n" << shaderSource.VertexSource << std::endl;
	std::cout << "FragmentSource: \n" << shaderSource.FragmentSource << std::endl;
	m_RendererID = CreateSharder(shaderSource.VertexSource, shaderSource.FragmentSource);
}

Shader::~Shader()
{
	glDeleteProgram(m_RendererID);
}

void Shader::Bind() const
{
	GLCall(glUseProgram(m_RendererID));
}

void Shader::Unbind() const
{
	glUseProgram(0);
}




ShaderProgramSource Shader::ParseSharder(const std::string& filePath)
{
	std::ifstream stream(filePath);

	enum class ShaderType {
		NONE = -1, VERTEX = 0, FRAGMENT = 1
	};

	ShaderType type = ShaderType::NONE;
	std::string line;
	std::stringstream ss[2];

	while (getline(stream, line)) {
		if (line.find("#shader") != std::string::npos) {
			if (line.find("vertex") != std::string::npos) {
				type = ShaderType::VERTEX;
			}
			else if (line.find("fragment") != std::string::npos) {
				type = ShaderType::FRAGMENT;
			}
		}
		else {
			ss[(int)type] << line << "\n";
		}
	}

	return { ss[0].str(), ss[1].str() };
}

unsigned int Shader::CreateSharder(const std::string& vertexSource, const std::string& fragmentSource)
{
	unsigned int program = glCreateProgram();
	unsigned int vs = CompileSharder(GL_VERTEX_SHADER, vertexSource);
	unsigned int fs = CompileSharder(GL_FRAGMENT_SHADER, fragmentSource);

	GLCall(glAttachShader(program, vs));
	GLCall(glAttachShader(program, fs));

	GLCall(glLinkProgram(program));
	GLCall(glValidateProgram(program));

	glDeleteShader(vs); // not really necessary 
	glDeleteShader(fs);

	return program;
}

unsigned int Shader::CompileSharder(unsigned int type, const std::string& source)
{
	unsigned int id = glCreateShader(type);
	const  char* src = source.c_str();
	GLCall(glShaderSource(id, 1, &src, NULL));
	GLCall(glCompileShader(id));

	/*Handle shader error */
	int result;
	glGetShaderiv(id, GL_COMPILE_STATUS, &result);
	if (result == GL_FALSE) {
		int length;
		glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
		char* message = (char*)alloca(length * sizeof(char));
		glGetShaderInfoLog(id, length, &length, message);
		std::cout << "Failed to compile shader: " << (type == GL_VERTEX_SHADER ? "vertex" : "fragment") << "shader!" << std::endl;
		std::cout << message << std::endl;

		glDeleteShader(id);
	}

	return id;
}

void Shader::SetUniform1i(const std::string& name, int value)
{
	const int location = GetUniformLocation(name);
	GLCall(glUniform1i(location, value));
}

void Shader::SetUniform4f(const std::string& name, float v0, float v1, float v2, float v3)
{
	const int location = GetUniformLocation(name);
	GLCall(glUniform4f(location, v0, v1, v2, v3));
}
void Shader::SetUniformMat4f(const std::string& name, const glm::mat4& matrix)
{
	const int location = GetUniformLocation(name);
	GLCall(glUniformMatrix4fv(location,1,GL_FALSE,&matrix[0][0]));
}

int Shader::GetUniformLocation(const std::string& name)
{
	GLCall(const int location = glGetUniformLocation(m_RendererID, name.c_str()));
	if (location == -1) {
		std::cout << "Warning: uniform " << name << " dosen't exists!" << std::endl;
	}
	return location;
}
